import path from 'path';
import fs from 'fs-extra';
import mount from 'koa-mount';
import koaBody from 'koa-body';
import serve from 'koa-static';
import Router from 'koa-router';
import Koa, { Response } from 'koa';
import { reqLog, resLog } from './log';
import { IDirFileInfo, IDirInnerDirInfo, IDirInnerFileInfo, TFileType, IRecordTravelSelf, TTravelReadDirFiles } from '../type';

import type { Files } from 'formidable';

interface IPackageRouterOptions {
    // 静态资源 URL
    staticResUrl?: string;
}

// Koa-Router 接收方法
type TRouterMethod = 'get' | 'delete' | 'options' | 'post' | 'put';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type IPackageRouterCallback = (ctx: Koa.ParameterizedContext, params: any, files: Files | undefined) => Promise<unknown> | void;

// 创建 Route 方法
type TCreateRoute = (method: TRouterMethod, url: string, callback?: IPackageRouterCallback, options?: IPackageRouterOptions) => void;

/**
 * 修复 Mac 下读取出多余文件的问题
 */
export function fixReaddir(dirpath: string): string[] {
    return fs.readdirSync(dirpath).filter(f => {
        return !f.startsWith('._') && !['.DS_Store'].includes(f);
    });
}

/**
 * 转换统一路径分隔符
 */
export function fixPathSeparator(path: string): string {
    return path.replace(/\/|\\/g, '/');
}

/**
 * 随机数
 */
export function getRandom(digit: number): string {
    const code = [];
    for (let i = 0; i < digit; i++) {
        code[i] = Math.floor(Math.random() * 10);
    }
    while (code[0] === 0) {
        code[0] += Math.floor(Math.random() * 10);
    }
    return code.join('');
}

/**
 * Response 封装
 */
export function packageResponse(response: Response, data: unknown = null, code = 0, statusCode = 200, errMsg = ''): void {
    response.status = statusCode;
    if (statusCode === 200) {
        const success = code === 0;
        response.body = { code, success, data, errMsg };
    } else {
        response.body = errMsg || 'Server Error!';
    }
    resLog(response);
}

/**
 * Router 封装
 */
export function packageRouter(router: Router, koa: Koa): TCreateRoute {
    // ...
    // options - staticResUrl 直接访问静态资源

    return (method, url, callback = undefined, options = {}) => {
        const { staticResUrl } = options;

        if (staticResUrl !== undefined) {
            koa.use(mount(url, serve(staticResUrl)));
            return;
        }

        router[method](url, koaBody({ multipart: true }), async (ctx, next) => {
            // ...
            let params;
            const files = ctx.request.files;

            switch (method) {
                case 'get':
                    params = ctx.request.query;
                    break;

                default:
                    if (typeof ctx.request.body === 'string') {
                        params = JSON.parse(ctx.request.body);
                    } else {
                        params = ctx.request.body || {};

                        // 合并动态路由参数
                        params = Object.assign(Object.create(null), params, ctx.params);
                    }
            }

            if (callback) {
                reqLog(ctx.request);
                await callback(ctx, params, files);
            }

            next();
        });
    };
}

/**
 * 递归遍历目录内部文件
 *
 * dirInnerAllFiles 存入文件和目录
 * dirInnerDirs     仅存入目录
 */
export function travelReadDirInnerAllFiles(dirpath: string, dirInnerAllFiles: IDirInnerFileInfo[], dirInnerDirs: IDirInnerDirInfo[]): void {
    const fileList = fixReaddir(dirpath);
    if (fileList && Array.isArray(fileList) && fileList.length > 0) {
        fileList.forEach(filename => {
            let fullpath = path.join(dirpath, filename);
            fullpath = fixPathSeparator(fullpath);
            const stat = fs.statSync(fullpath);
            if (stat.isDirectory()) {
                // ...
                const fileInfo: IDirInnerFileInfo = {
                    type: 'dir',
                    fileType: null,
                    path: fullpath,
                    ext: null,
                    dirname: filename,
                    filename: null,
                    child: [],
                };

                const dirInfo: IDirInnerDirInfo = {
                    type: 'dir',
                    path: fullpath,
                    dirname: filename,
                    child: [],
                };

                dirInnerAllFiles.push(fileInfo);
                dirInnerDirs.push(dirInfo);

                travelReadDirInnerAllFiles(fullpath, fileInfo.child || [], dirInfo.child || []);

                if (fileInfo.child && fileInfo.child.length <= 0) {
                    fileInfo.child = null;
                }
                if (dirInfo.child && dirInfo.child.length <= 0) {
                    dirInfo.child = null;
                }

                // ...
            } else if (stat.isFile()) {
                const ext = filename.split('.').pop()?.toLowerCase() || null;

                const fileInfo: IDirInnerFileInfo = {
                    type: 'file',
                    fileType: 'other',
                    path: fullpath,
                    ext,
                    dirname: null,
                    filename,
                    child: [],
                };
                if (ext) {
                    fileInfo.fileType = getFileType(ext);
                }
                dirInnerAllFiles.push(fileInfo);
            }
        });
    }
}

/**
 * 递归遍历目录内部，返回文件和目录列表
 *
 * 支持深度遍历，但是所有结果都是放在一层数组内
 */
function travelReadDirFiles(dirpath: string, isDeep: boolean, fileList: TTravelReadDirFiles) {
    if (isDeep) {
        const list = fixReaddir(dirpath);
        if (list && Array.isArray(list) && list.length > 0) {
            for (const filename of list) {
                const filepath = path.join(dirpath, filename);
                fileList.push({ filename, filepath });
                const stat = fs.statSync(filepath);
                if (stat.isDirectory()) {
                    travelReadDirFiles(filepath, isDeep, fileList);
                }
            }
        }
    } else {
        const list = fixReaddir(dirpath) || [];
        for (const filename of list) {
            const filepath = path.join(dirpath, filename);
            fileList.push({ filename, filepath });
        }
    }
}

/**
 * 遍历目录内部文件
 *
 * 返回结果较细致，文件就是文件，目录就是目录，而且还有文件分类
 */
export function readDirInnerFiles(dirpath: string, isDeep: boolean): IDirFileInfo {
    const result: IDirFileInfo = {
        dirs: [],
        files: [],
        category: {
            pdf: [],
            excel: [],
            image: [],
            video: [],
            audio: [],
            other: [],
        },
    };

    const fileList: TTravelReadDirFiles = [];
    travelReadDirFiles(dirpath, isDeep, fileList);

    fileList.forEach(({ filename, filepath }) => {
        const fullpath = fixPathSeparator(filepath);
        const stat = fs.statSync(fullpath);

        if (stat.isDirectory()) {
            // ...
            const fileInfo: IDirInnerFileInfo = {
                type: 'dir',
                fileType: null,
                path: fullpath,
                ext: null,
                dirname: filename,
                filename: null,
                child: null,
            };

            result.dirs.push(fileInfo);

            // ...
        } else if (stat.isFile()) {
            const ext = filename.split('.').pop()?.toLowerCase() || null;

            const fileInfo: IDirInnerFileInfo = {
                type: 'file',
                fileType: 'other',
                path: fullpath,
                ext,
                dirname: null,
                filename,
                child: null,
            };

            if (ext) {
                const fileType = getFileType(ext);
                fileInfo.fileType = fileType;
                if (fileType === 'image') {
                    result.category.image.push(fileInfo);
                } else if (fileType === 'excel') {
                    result.category.excel.push(fileInfo);
                } else if (fileType === 'video') {
                    result.category.video.push(fileInfo);
                } else if (fileType === 'audio') {
                    result.category.audio.push(fileInfo);
                } else if (fileType === 'pdf') {
                    result.category.pdf.push(fileInfo);
                } else {
                    result.category.other.push(fileInfo);
                }
            }

            result.files.push(fileInfo);
        }
    });

    return result;
}

/**
 * 读取目录内部文件，按对象格式输出
 *
 * 如果是文件，那么就是 filename:filepath；
 * 如果是目录，那么就是对象 IRecordTravelSelf
 */
export function readDirInnerFilesToObject(anypath: string, object: IRecordTravelSelf) {
    for (const item of fixReaddir(anypath)) {
        const p = anypath + '/' + item;
        if (fs.statSync(p).isDirectory()) {
            object[item] = { dirpath: p };
            readDirInnerFilesToObject(p, object[item] as IRecordTravelSelf);
        } else {
            object[item] = p;
        }
    }
}

/**
 * 获取文件类型
 */
function getFileType(ext: string): TFileType {
    const pdfExt = ['pdf'];
    const excelExt = ['xls', 'xlsx'];
    const imageExt = ['png', 'jpeg', 'jpg', 'gif', 'webp', 'ico'];
    const videoExt = ['mp4', 'avi', 'mkv', 'flv', 'vob', 'rmvb', 'wmv', 'mov'];
    const audioExt = ['mp3'];

    if (imageExt.includes(ext)) {
        return 'image';
    } else if (excelExt.includes(ext)) {
        return 'excel';
    } else if (videoExt.includes(ext)) {
        return 'video';
    } else if (audioExt.includes(ext)) {
        return 'audio';
    } else if (pdfExt.includes(ext)) {
        return 'pdf';
    } else {
        return 'other';
    }
}

/**
 * 文件名插入字符
 *
 * 例如 F:/a/b/c.txt -> F:/a/b/c.char.txt
 */
export function filenameAppendChar(filepath: string, char: string): string {
    filepath = fixPathSeparator(filepath);
    const a = filepath.split('/');
    const b = a.pop() as string; // 文件名
    const c = b.split('.');
    const d = c.pop(); // 文件扩展符
    a.push(c.join('.') + char + '.' + d);
    return a.join('/');
}

/**
 * 使用流写入文件
 */
export function writeFileByStream(inputPath: string, outputPath: string): Promise<void> {
    return new Promise((resolve, reject) => {
        const inputStream = fs.createReadStream(inputPath);
        const outputStream = fs.createWriteStream(outputPath);

        inputStream.on('error', e => {
            reject(e);
        });
        outputStream.on('error', e => {
            reject(e);
        });

        outputStream.on('finish', () => {
            resolve();
        });

        inputStream.pipe(outputStream);
    });
}

/**
 * 递归排序文件
 */
export function travelSortFile(list: IDirInnerDirInfo[]): void {
    if (list.length > 0) {
        list.sort((a, b) => {
            if (a.dirname.toLocaleLowerCase() < b.dirname.toLocaleLowerCase()) {
                return -1;
            }
            if (a.dirname.toLocaleLowerCase() > b.dirname.toLocaleLowerCase()) {
                return 1;
            }
            return 0;
        });

        list.forEach(item => {
            if (item.child) {
                travelSortFile(item.child);
            }
        });
    }
}
